package org.fhm.zdte.client.cmd;

import org.fhm.substrate.annotation.Component;
import org.fhm.substrate.annotation.Setup;
import org.fhm.substrate.service.LoggerHandler;
import org.fhm.substrate.standard.ILogger;
import org.fhm.zdte.common.ability.IGetIdentification;
import org.fhm.zdte.common.ability.IRefreshBaseTime;
import org.fhm.zdte.common.config.ProtocolConfiguration;
import org.fhm.zdte.common.constant.protocol.ConnectStatus;
import org.fhm.zdte.common.constant.protocol.PacketType;
import org.fhm.zdte.common.pojo.Address;
import org.fhm.zdte.common.protocol.AbstractMessageStandard;
import org.fhm.zdte.common.standard.IReceiveMessageManager;
import org.fhm.zdte.common.standard.ISendMessageManager;
import org.fhm.zdte.common.util.CommonUtil;

import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ScheduledFuture;

/**
 * Heart-Beat command processor.
 *
 * @author 谭波
 * @since 2023/12/24
 */
@Component("HEARTBEAT")
public class HeartbeatClientCmd extends AbstractClientCmd implements IRefreshBaseTime {

    private final ILogger logger = LoggerHandler.getLogger(HeartbeatClientCmd.class);

    @Setup
    private IGetIdentification iGetIdentification;

    @Setup
    private CommonUtil commonUtil;

    @Setup
    private ProtocolConfiguration protocolConfiguration;

    @Setup
    private Map<String, ISendMessageManager> sendMessageManagerContainer;

    @Setup
    private Map<String, IReceiveMessageManager> receiveMessageManagerContainer;

    @Override
    public void receiveCmd(Address address, AbstractMessageStandard message) {
        if (abstractMessageMonitor.isAck(message)) {
            refreshBaseTime(message.getFrom());
        } else {
            sendDealTask.getTaskAndSubmit(
                    abstractMessageWrappers
                            .getAckMessageByPacketType(
                                    PacketType.HEARTBEAT,
                                    null,
                                    iGetIdentification.getIdentification(),
                                    "",
                                    null,
                                    null,
                                    ""),
                    address
            );
        }
    }


    @Override
    public void sendCmd(Address address, AbstractMessageStandard message) {
        if (Objects.nonNull(address)) {
            iChannelDispatcher.obtainChannelManager(address)
                    .sendMessage(message, address);
            return;
        }
        int heartbeatTimeout = clientInitialConfiguration.getHeartbeatTimeout() * 1000;
        sendCheckMessage(connectUserContainer, heartbeatTimeout, false);
        sendCheckMessage(connectedUserContainer, heartbeatTimeout, true);
    }

    private void sendCheckMessage(
            Map<String, Address> container,
            int heartbeatTimeout, boolean isConnected
    ) {
        Iterator<Map.Entry<String, Address>> iter = container.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<String, Address> entry = iter.next();
            String target = entry.getKey();
            Address address = entry.getValue();
            if (ConnectStatus.isConnecting(address.getStatus())) {
                ScheduledFuture<?> obj;
                if (
                        isConnected
                                && Objects.nonNull((obj = connectTaskContainer.get(target)))
                                && commonUtil.getCurTimeMillisSecond() > (
                                (heartbeatTimeout + timeRecordContainer.get(target))
                        )
                ) {
                    synchronized (obj) {
                        if (
                                Objects.nonNull(connectTaskContainer.get(target))
                                        && commonUtil.getCurTimeMillisSecond() >
                                        (heartbeatTimeout + timeRecordContainer.get(target))
                        ) {
                            ScheduledFuture<?> task = connectTaskContainer.remove(target);
                            if (commonUtil.cancelTask(task)) {
                                iter.remove();
                                logger.info("delete user record for connecting");
                                timeRecordContainer.remove(target);
                            }
                        }
                    }
                }
                return;
            }
            iChannelDispatcher.obtainChannelManager(address)
                    .sendMessage(
                            abstractMessageWrappers
                                    .getMessageByPacketType(
                                            PacketType.HEARTBEAT,
                                            null,
                                            iGetIdentification.getIdentification(),
                                            "",
                                            "",
                                            null, null,
                                            null,
                                            null),
                            address
                    );
            Long time = timeRecordContainer.get(target);
            if (Objects.nonNull(time)) {
                synchronized (time) {
                    if (Objects.nonNull(timeRecordContainer.get(target))) {
                        if (
                                commonUtil.getCurTimeMillisSecond() > (
                                        (heartbeatTimeout + timeRecordContainer.get(target))
                                )
                        ) {
                            timeRecordContainer.remove(target);
                            iter.remove();
                            logger.info("the user with the identification is {} has been disconnected", target);
                            clearMessageCache(container, target);
                        }
                    }
                }
            }
        }
    }

    private void clearMessageCache(Map<String, Address> container, String from) {
        sendMessageManagerContainer
                .values()
                .forEach(
                        manager -> {
                            if (Objects.nonNull(container.get(from))) {
                                synchronized (from) {
                                    if (Objects.nonNull(container.get(from))) {
                                        manager.clearMessageBuffer(from);
                                    }
                                }
                            }
                        }
                );
        receiveMessageManagerContainer
                .values()
                .forEach(
                        manager -> {
                            if (Objects.nonNull(container.get(from))) {
                                synchronized (from) {
                                    if (Objects.nonNull(container.get(from))) {
                                        manager.clearMessageBuffer(from);
                                    }
                                }
                            }
                        }
                );
    }

    @Override
    public void refreshBaseTime(String from) {
        Long time;
        if (Objects.nonNull((time = timeRecordContainer.get(from)))) {
            synchronized (time) {
                if (Objects.nonNull(timeRecordContainer.get(from)))
                    timeRecordContainer.put(from, commonUtil.getCurTimeMillisSecond());
            }
        }
    }
}
